﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Forms;

namespace Tzync
{
   public class Rollback
    {
        private long spaceAllocated = 200 * 1024 * 1024; //200 mb space on computer for storing rollback files     
        private long spaceRemaining;    //space left on computer for storing rollback files  
        private long spaceBuffer = 100 * 1024 * 1024;   //space buffer in case computer has less space than allocated
        private string rbLocation = @"C:\Tzync\Rollback\";  //location of rollback folder
        private string rbMetadataLocation;  //location of rollback metadata file
        private List<RollbackFile> rbList;  //list of rollback files
        private List<Difference> differenceToRollbackList;    //list of differences to store for rollback
        private List<TreeNode> arrNodeList;    //list of treenodes
        private SystemType system;  //system user is on (host or guest)
        private StreamWriter metadataWriter;    //for writing to text files
        private StreamReader metadataReader;    //for reading from text files
        private List<Difference> differenceToSync = new List<Difference>(); //differences to sync after rollback
        private int fileVersion;    //version of rollback file
        private Profile profile;    //profile in use

        public Rollback()
        {
        }

       //sets up and loads rollback metadata
        public Rollback(Profile newProfile)
        {
            rbList = new List<RollbackFile>();
            profile = newProfile;
            rbLocation += profile.ProfileName;  //location to store rollback files for this profile
            rbMetadataLocation = rbLocation + @"\" + profile.ProfileName;   //location of rollback metadata for this profile
            system = profile.SystemName;

            //second run onwards
            if (profile.HasA && profile.HasB)
                ReadRollMetadata(); //read from existing metadata

            //first run on computer
            else if (profile.HasA && !profile.HasB)
            {
                if (!File.Exists(rbMetadataLocation))   //if metadata is not created yet
                {
                    AdjustSpaceAllocated();
                    spaceRemaining = spaceAllocated;
                    SetupRollbackMetadata();    //sets up metadata
                }
            }
        }

        // creates and writes setup information to rollback metadata file
        public void SetupRollbackMetadata()
        {
            Directory.CreateDirectory(rbLocation);  //create rollback directory
            if (!File.Exists(rbMetadataLocation))
            {
                metadataWriter = new StreamWriter(rbMetadataLocation);
                metadataWriter.WriteLine(spaceAllocated + "|" + spaceRemaining); //write setup information
                metadataWriter.Close();
            }
            else
                throw new FileNotFoundException(rbMetadataLocation);
        }

        //updates rollback metadata file
        public void UpdateRollbackMetadata()
        {
            if (rbList.Count() > 0) //if there are rollback files to write to metadata
            {
                if (File.Exists(rbMetadataLocation))
                {
                    metadataWriter = new StreamWriter(rbMetadataLocation, false);
                    metadataWriter.WriteLine(spaceAllocated + "|" + spaceRemaining);

                    for (int i = 0; i < rbList.Count; i++)
                    {
                        if (rbList[i].isDone() == false)   //write each rollback file to rollback metadata 
                        {
                            metadataWriter.WriteLine(rbList[i].GetSource() + "|" + rbList[i].GetDestination() + "|" +
                                rbList[i].GetLastSyncTime().ToString() + "|" + rbList[i].GetId() + "|" + rbList[i].GetVersion());
                        }
                        else if (rbList[i].isDone() == true)    //remove files that have been rollback'ed
                            rbList.RemoveAt(i);
                    }

                    metadataWriter.Close();
                }
                else
                    throw new FileNotFoundException(rbMetadataLocation);
            }
        }

       //reads from rollback metadata file
        public void ReadRollMetadata()
        {
            if (File.Exists(rbMetadataLocation))
            {
                RollbackFile rbFile;    //a rollback file
                //for processing text files
                String[] data;
                String line;
                char[] charSeparators = new char[] { '|' }; // '|' is the delimiter
                metadataReader = new StreamReader(rbMetadataLocation);

                line = metadataReader.ReadLine();
                data = line.Split(charSeparators);

                spaceAllocated = Convert.ToInt32(data[0]);   //space allocated
                spaceRemaining = Convert.ToInt32(data[1]);  //space remaining

                line = metadataReader.ReadLine();

                //loads rollback files into memory
                while (line != null)
                {
                    data = line.Split(charSeparators);
                    rbFile = new RollbackFile();
                    rbFile.SetSource(data[0]);  //address of file in rollback folder
                    rbFile.SetDestination(data[1]); //address of file after rollback
                    rbFile.SetSyncTime(Convert.ToDateTime(data[2]));    //date last synced of file
                    rbFile.SetId(Convert.ToInt32(data[3]));    //id of file
                    rbFile.SetVersion(Convert.ToInt32(data[4]));    //version of file

                    rbList.Add(rbFile);
                    line = metadataReader.ReadLine();
                }

                AdjustSpaceAllocated();
                metadataReader.Close();
            }
            else
                throw new FileNotFoundException(rbMetadataLocation);
        }

       //creates rollback files before sync for future rollback
        public void AddRollback(List<Difference> differenceList, List<TreeNode> arrNodes, SystemType newSystem)
        {
            system = newSystem;
            arrNodeList = arrNodes;
            differenceToRollbackList = new List<Difference>();
            FileInfo file;   

            foreach (Difference d in differenceList)    //process each difference found
            {
                //only back up files that are going to be replaced on this system
                if (d.GetPairType().First == DirectionType.HDToSystem)
                {
                    //ignore creation and del-del conflicts
                    if (d.GetPairType().Second == OperationType.Deletion ||
                        d.GetPairType().Second == OperationType.Modification ||
                        d.GetPairType().Second == OperationType.ModModConflict ||
                        d.GetPairType().Second == OperationType.CreationCreationConflict ||
                        d.GetPairType().Second == OperationType.ModDeleteConflict)
                    {
                        if (d.GetPairType().Second == OperationType.ModDeleteConflict)
                        {
                            file = d.GetSysFile();
                            if (file.Exists == true)    //Del on other computer, Mod on this computer
                                differenceToRollbackList.Add(d);
                        }

                        else
                            differenceToRollbackList.Add(d);
                    }
                }
            }

            foreach (Difference d in differenceToRollbackList)
            {
                ReviewRollbackList(d);   //free up space for new files
                AddRollbackFile(d);  //add new files to list of rollback files
                UpdateRollbackMetadata();   //update rollback metadata with new files
            }
        }

       //deletes old files to make way for newer files
        public void ReviewRollbackList(Difference newDiff)
        {
            if (differenceToRollbackList != null)
            { 
                long spaceUsed = 0;

                //calculate space used by new file
                if (newDiff.GetItemType() == Type.File)
                {
                    if (system == SystemType.Host)
                        spaceUsed += newDiff.GetPartnerFile().getSizeA();
                    else
                        spaceUsed += newDiff.GetPartnerFile().getSizeB();
                }

                //free up space by deleting older files
                if (spaceUsed > spaceRemaining)
                {
                    RollbackFile rbFile;

                    while (spaceUsed > spaceRemaining)
                    {
                        if (rbList.Count > 0)
                        {
                            rbFile = rbList.First();    //choose oldest file first
                            File.Delete(rbFile.GetDestination());   //delete physical file
                            rbList.Remove(rbFile);  //remove from list of rollback files
                            spaceRemaining += rbFile.GetSize();
                        }
                    }
                }
            }
        }

       //adds new files to list of rollback files
        public void AddRollbackFile(Difference newDiff)
        {
            long spaceUsed = 0;
            RollbackFile rbFile ;

            rbFile = new RollbackFile();    //create a new rollback file
            if (newDiff.GetItemType() == Type.File && newDiff.GetPartnerFile() != null) //only accept files
            {
                rbFile.SetId(newDiff.GetPartnerFile().getId());
                rbFile.SetIsDone(false);

                if (system == SystemType.Host)  //host
                {
                    spaceUsed = newDiff.GetPartnerFile().getSizeA();  //update total space used by rollback files         
                    rbFile.SetDestination(newDiff.GetPartnerFile().getFullPathA());
                    rbFile.SetSyncTime(newDiff.GetPartnerFile().getSyncA());
                    rbFile.SetSize(newDiff.GetPartnerFile().getSizeA());
                }

                else if (system == SystemType.Guest)    //guest
                {
                    spaceUsed = newDiff.GetPartnerFile().getSizeB();  //update total space used by rollback files
                    rbFile.SetDestination(newDiff.GetPartnerFile().getFullPathB());
                    rbFile.SetSyncTime(newDiff.GetPartnerFile().getSyncB());
                    rbFile.SetSize(newDiff.GetPartnerFile().getSizeB());
                }

                foreach (RollbackFile r in rbList)
                {
                    if (r.GetDestination() == rbFile.GetDestination())
                    {
                        fileVersion = r.GetVersion() + 1;
                        rbFile.SetVersion(fileVersion);
                        break;
                    }
                }

                rbFile.SetSource(rbLocation + @"\" + rbFile.GetId() + rbFile.GetVersion() + newDiff.GetPartnerFile().getName());

                if (Directory.Exists(rbLocation) == false)  //create parent folders if they don't exist
                    System.IO.Directory.CreateDirectory(rbLocation);

                if (File.Exists(rbFile.GetDestination()) == true)   //file must exist on this computer
                    File.Copy(rbFile.GetDestination(), rbFile.GetSource(),true); //make a rollback copy of file, only keep 1 copy
                
                spaceRemaining -= spaceUsed;    //update space remaining
                rbList.Add(rbFile); //update list of rollback files
            }
        }

       //updates rollback files
        public void UpdateRollbackFile(String oldRootAddress, String newRootAddress)
        {
            String address;
            int lengthOfAddress = oldRootAddress.Length; 
            foreach (RollbackFile r in rbList)
            {
                address = r.GetDestination().Remove(0,lengthOfAddress);
                address = newRootAddress + address;
                r.SetDestination(address);
            }
        }

       //perform a rollback of file
        public void PerformRollback(List<String> filesToRollback, List<TreeNode> arrNodes, SystemType currentSystem)
        {
            TreeNode tNode;
            DateTimeParser dtp;
            FileInfo fileInfo;
            arrNodeList = arrNodes;
            system = currentSystem;
            differenceToSync = new List<Difference>();

            foreach (String s in filesToRollback)   //list of files to rollback
            {
                foreach (RollbackFile r in rbList)
                {
                    if (r.GetSource() == s) //find file in metadata and retrieve its information
                    {
                        //do actual rollback
                        File.Delete(r.GetDestination());
                        File.Move(r.GetSource(), r.GetDestination());
                        tNode = arrNodeList[r.GetId()];

                        //update Tzync metadata
                        if (system == SystemType.Host)  //host
                        {
                            if (arrNodeList[r.GetId()].getVersionA().First == -1 && arrNodeList[r.GetId()].getVersionA().Second == 0)
                                arrNodeList[r.GetId()].getVersionA().First++;

                            arrNodeList[r.GetId()].getVersionA().First++;
                            fileInfo = new FileInfo(tNode.getFullPathA());
                            dtp = new DateTimeParser(fileInfo.LastWriteTime);
                            arrNodeList[r.GetId()].setSyncA(dtp.GetNewDateTime());
                        }
                        else    //guest
                        {
                            if (arrNodeList[r.GetId()].getVersionB().First == 0 && arrNodeList[r.GetId()].getVersionB().Second == -1)
                                arrNodeList[r.GetId()].getVersionB().Second++;

                            arrNodeList[r.GetId()].getVersionB().Second++;
                            fileInfo = new FileInfo(tNode.getFullPathB());
                            dtp = new DateTimeParser(fileInfo.LastWriteTime);
                            arrNodeList[r.GetId()].setSyncB(dtp.GetNewDateTime());
                        }

                        //add to list of differences to sync
                        Pair<DirectionType, OperationType> newPair = new Pair<DirectionType,OperationType>(DirectionType.SystemToHD, OperationType.Modification);
                        Difference newDiff = new Difference(fileInfo, tNode, newPair);
                        differenceToSync.Add(newDiff);

                        spaceRemaining += r.GetSize();  //update space remaining
                        r.SetIsDone(true);  //rollback entry can be deleted from metadata
                        break;
                    }
                }
            }

            UpdateRollbackMetadata();   //update rollback metadata
        }

       //performs a sync after a rollback is executed
        public void SyncAfterRollback()
        {
            String rootAddress = "";
            if (profile.SystemName == SystemType.Host)
                rootAddress = profile.RootAddressA;
            else
                rootAddress = profile.RootAddressB;
            Sync sync = new Sync(differenceToSync, arrNodeList, system, rootAddress);
            sync.Reconcile();
        }

       //removes rollback from profile when profile is removed
        public void RemoveRollback(String newProfileName)
        {
            DirectoryInfo profileDir = new DirectoryInfo(rbLocation + @"\" + newProfileName);
            profileDir.Delete(true);
        }

       //checks if there's enough space for rollback files to be stored
        private void AdjustSpaceAllocated()
        {
            DriveInfo cDrive = new DriveInfo("C");  //c drive on computer
            long spaceAvailable = cDrive.AvailableFreeSpace;    //available space                

            if (spaceRemaining > spaceAvailable)    //check if user has enough space on computer for rollback
                spaceRemaining = spaceAvailable - spaceBuffer;  //adjust allocated space if necessary
        }

        //returns list of rollback files on computer
        public List<RollbackFile> GetRollbackList()
        {
            return rbList;
        }

        //returns space allocated to rollback on computer
        public long GetSpaceAllocated()
        {
            return spaceAllocated;
        }

        //returns space left for rollback on computer
        public long GetSpaceRemaining()
        {
            return spaceRemaining;
        }

    }   //end of Rollback class
}